const exportObject = {
  ...require('./utils/express-wss'),
  ...require('./utils/run-script'),
  ...require('./common'),
  /**
   * 向页面注入script标签
   *
   * @param {object | string}param0 可以直接为Js代码片段
   * @param {string}param0.script 要注入的Javascript内容
   * @param {string}param0.src 要注入的src
   * @param {string}param0.filePath 要注入的js文件地址
   */
  injectScript: function (param0) {
    const Fs = require('fs-extra')
    const scriptElement = document.createElement('script')
    scriptElement.type = 'text/javascript'
    if (param0 instanceof String) {
      scriptElement.appendChild(document.createTextNode(param0))
    } else {
      const { script, src, filePath } = param0
      if (script) {
        scriptElement.appendChild(document.createTextNode(script))
      } else if (src) {
        scriptElement.src = src
      } else if (filePath && Fs.pathExistsSync(filePath)) {
        const js = Fs.readFileSync(filePath, { encoding: 'utf-8' })
        scriptElement.appendChild(document.createTextNode(js))
      }
    }
    document.body.appendChild(scriptElement)
  },

  /**
   * 向页面注入style
   *
   * @param {object} param0 选项
   * @param {string} param0.css 要注入的css内容
   * @param {string} param0.href 要注入的hef
   * @param {string} param0.filePath 要注入的css文件地址
   */
  injectCss: function ({ css, href, filePath }) {
    const Fs = require('fs-extra')
    const head = document.getElementsByTagName('head')[0]
    if (href) {
      const cssElement = document.createElement('link')
      cssElement.setAttribute('rel', 'stylesheet')
      cssElement.setAttribute('media', 'all')
      cssElement.setAttribute('type', 'text/css')
      cssElement.setAttribute('href', href)
      head.appendChild(cssElement)
    } else {
      const styleElement = document.createElement('style')
      const content = css || Fs.readFileSync(filePath, { encoding: 'utf-8' })
      styleElement.appendChild(document.createTextNode(content))
      head.appendChild(styleElement)
    }
  },

  /**
   * 获得相对于工程或者exe文件路径的地址，该地址会自动创建
   *
   * @param {string | string[]} relativePath 相对于 工程 或者 exe所在文件夹 的位置
   * @param {string} [mode=Dir] - "Dir"(默认) - 这是个文件夹 ; "File"-这是个文件;
   * @returns {string} 如果已打包，则在exe所在位置/data目录下。如果未打包，则在工程的/data目录。
   */
  dir: function (relativePath = 'data', mode = 'Dir') {
    const Path = require('path')
    const Fs = require('fs-extra')
    // 需要@electron/remote模块
    // 需要在background.js中运行以下内容
    // const remoteMain = require('@electron/remote/main')
    // remoteMain.initialize()
    // remoteMain.enable(webContents)
    const cb = (app) => {
      try {
        let dirPath = app.isPackaged
          ? Path.normalize(Path.dirname(app.getPath('exe'))) // 如果已打包返回exe的目录
          : process.cwd() // 否则是Nodejs项目的根目录
        dirPath =
          Object.prototype.toString.call(relativePath) === '[object String]'
            ? Path.join(dirPath, relativePath)
            : Path.join(dirPath, ...relativePath)
        if (/dir/i.test(mode)) {
          Fs.ensureDirSync(dirPath)
        } else {
          Fs.ensureFileSync(dirPath)
        }
        return dirPath
      } catch (error) {
        let dirPath = app.isPackaged
          ? Path.join(process.cwd(), '../../') // 退到asar以外
          : process.cwd()
        dirPath =
          Object.prototype.toString.call(relativePath) === '[object String]'
            ? Path.join(dirPath, relativePath)
            : Path.join(dirPath, ...relativePath)
        if (/dir/i.test(mode)) {
          Fs.ensureDirSync(dirPath)
        } else {
          Fs.ensureFileSync(dirPath)
        }
        return dirPath
      }
    }
    try {
      const { app } = require('@electron/remote')
      return cb(app)
    } catch (error) {
      try {
        const { app } = require('electron')
        return cb(app)
      } catch (error) {
        // const exeDir = Path.join(
        //   __dirname
        //   // '../' /* node_modules */,
        //   // '../' /* app.asar.unpacked */,
        //   // '../' /* resources */,
        //   // '../' /* win-unpacked */
        // )
        const exeDir = process.cwd()
        return Object.prototype.toString.call(relativePath) ===
          '[object String]'
          ? Path.normalize(Path.resolve(exeDir, relativePath)) // 如果是string
          : Path.normalize(Path.resolve(exeDir, ...relativePath)) // 如果是[]
      }
    }
  },

  /**
   *
   * @param {string}[mode='save'] 或者Open
   * @see {@link https://www.electronjs.org/docs/latest/api/dialog#dialogshowopendialogbrowserwindow-options | 打开文件 / 文件夹}
   * @see {@link https://www.electronjs.org/docs/latest/api/dialog#dialogshowsavedialogbrowserwindow-options | 保存文件}
   * @param  {object}options 选项
   * @returns {Promise<object>} .canceled Boolean - whether or not the dialog was canceled.
   * .filePaths String[] 只有在open模式下返回数组，如果没有选择任何文件，返回空数组
   *  .filePath  String[] 只有在save模式下返回数组
   */
  dialogF: function (mode = 'save', options) {
    const { dialog } = require('@electron/remote')
    if (/open/i.test(mode)) {
      return dialog.showOpenDialog(options)
    } else {
      return dialog.showSaveDialog(options)
    }
  },

  /**
   *
   * @param {string}[mode='save'] 或者Open
   * @see {@link https://www.electronjs.org/docs/latest/api/dialog#dialogshowopendialogsyncbrowserwindow-options | 打开文件 / 文件夹}
   * @see {@link https://www.electronjs.org/docs/latest/api/dialog#dialogshowsavedialogsyncbrowserwindow-options | 保存文件}
   * @param {object}options 选项
   * @returns {string[] | string | undefined} 如果是是Open模式，返回String[];。如果Save模式，返回String; 如果点击了取消，则
   */
  dialogFSync: function (mode = 'save', options) {
    const { dialog } = require('@electron/remote')
    if (/open/i.test(mode)) {
      return dialog.showOpenDialogSync(options)
    } else {
      return dialog.showSaveDialogSync(options)
    }
  },

  /**
   * @function 执行一个command并返回输出内容
   * @param {string}[command="pip list"] 要运行的command
   * @returns {Promise<string>} 输出的内容
   */
  exec(command = 'pip list') {
    return new Promise((resolve, reject) => {
      const { exec } = require('child_process')
      // eslint-disable-next-line node/handle-callback-err
      const child = exec(command, (err, stdout) => {
        resolve(stdout)
      })
      child.on('error', (err) => reject(err))
    })
  },

  /**
   * 对一个对图片进行ocr识别，需要Python 3.x和easyocr (pip包)
   *
   * @param {object}root0 选项
   * @param {string|HTMLElement}root0.img 文件的路径，或者DOM元素
   * @param {string}[root0.atDir="/node_modules/abertgao.common-electron/data/ocr"] 在哪个文件夹运行,如果打包electron asar需要特别注意修改，asar为只读文件
   * @returns {Promise<string>} 返回图片识别结果
   */
  async easyOcr({ img, atDir = require('path').join(__dirname, 'data/ocr') }) {
    let tempImgPath = `${new Date().getTime()}-${~~(Math.random() * 100000)}`
    const Fs = require('fs-extra')
    const Path = require('path')
    const { isHTMLElement, getImgBase64, runScript, exec } = exportObject
    const pythonVersion = await exec('python -V')
    if (!/Python\s+3\./.test(pythonVersion)) {
      throw new Error('Python 3.x not installed')
    }
    const pipList = await exec('pip list')
    if (!/easyocr\s+\d/.test(pipList)) {
      throw new Error('easyocr not installed')
    }
    // eslint-disable-next-line no-unused-expressions
    ;('------------把文件临时拷过来-----------')
    if (Object.prototype.toString.call(img) === '[object String]') {
      // 如果是路径
      if (Fs.pathExistsSync(img)) {
        const ext = Path.extname(img)
        tempImgPath = Path.join(atDir, `${tempImgPath}${ext}`) // ext 不用加.
        Fs.copySync(img, tempImgPath, { overwrite: true })
      } else {
        throw new Error("File path does't exist:" + img)
      }
    } else if (isHTMLElement(img)) {
      // 如果是HtmlElement
      const { ext, data } = getImgBase64(img)
      tempImgPath = Path.join(atDir, `${tempImgPath}.${ext}`)
      Fs.writeFileSync(tempImgPath, data, { encoding: 'base64' })
    }
    // eslint-disable-next-line no-unused-expressions
    ;('------------对文件进行ocr解析-----------')
    await runScript({
      filePath: Path.join(__dirname, 'python/ocr.py'),
      msgTo: tempImgPath,
      atDir
    })
    Fs.removeSync(tempImgPath)
    const txtPath = tempImgPath + '.txt'
    if (Fs.pathExistsSync(txtPath)) {
      const result = Fs.readJSONSync(txtPath)
      Fs.removeSync(txtPath)
      return result
    } else {
      throw new Error('No result')
    }
  },
  /**
   * 对一个对图片进行ocr识别，使用tesseract.js
   *
   * @param {object}root0 选项
   * @param {string|HTMLElement}root0.img 文件的路径，或者DOM元素
   * @param {string}[root0.atDir="/node_modules/abertgao.common-electron/data/ocr"] 在哪个文件夹运行,如果打包electron asar需要特别注意修改，asar为只读文件
   * @returns {Promise<string>} 返回图片识别结果
   */
  async ocr({ img, atDir = require('path').join(__dirname, '/data/ocr') }) {
    // const tempImgPath = `${new Date().getTime()}-${~~(Math.random() * 100000)}`
    const Fs = require('fs-extra')
    const Path = require('path')
    const { isHTMLElement, getImgBase64, runScript } = exportObject
    let newImg
      // eslint-disable-next-line no-unused-expressions
    ;('------------把文件临时拷过来-----------')
    if (Object.prototype.toString.call(img) === '[object String]') {
      // 如果是路径
      if (Fs.pathExistsSync(img)) {
        newImg = img
      } else {
        throw new Error("File path does't exist:" + img)
      }
    } else if (isHTMLElement(img)) {
      // 如果是HtmlElement
      newImg = getImgBase64(img).base64
    }
    // eslint-disable-next-line no-unused-expressions
    ;('------------对文件进行ocr解析-----------')
    return await runScript({
      filePath: Path.join(__dirname, 'scripts/tesseract.js'),
      msgTo: { img: newImg },
      atDir
    })
  },
  /**
   * 把Csv格式专为Object[]
   *
   * @param {string | Array[]} csv 从csv文件读取的数组
   * @param {number} [titleStart=0] 哪个index是title(从0开始)
   * @param {number} [dataStart=titleStart + 1] 哪个(含)index之后是数据
   * @param {string | RegExp} [splitter=","] csv文件的每行中各值之间的分割符
   * @returns {object[]} 转换结果
   */
  csvToObjArr(
    csv = [],
    titleStart = 0,
    dataStart = titleStart + 1,
    splitter = ','
  ) {
    if (/String/i.test(Object.prototype.toString.call(csv))) {
      // 如果是string，先转换成Array
      csv = [...csv.split(/\n\r*/g)].map((row) => [...row.split(splitter)])
    }
    const titles = csv[titleStart]
    const spliced = csv.slice(dataStart)
    return spliced.map((row) => {
      const obj = {}
      for (const i in row) {
        obj[titles[i]] = row[i]
      }
      return obj
    })
  },
  /**
   * 把Object[]转换回Csv格式的数组
   *
   * @param {object[]} rows 传入一个object[]，至少要有一行数据
   * @param {number}[titlesFrom=0] 从哪个index读取titles
   * @param  {Function}[parser = (value, key, i) => value || ''] 传入(值 ,键, 行index)，返回该位置的值
   * @returns {Array[]}返回csv可用的数组，首行为keys
   */
  objArrToCsv(
    rows = [],
    titlesFrom = 0,
    parser = (value, key, i) => value || ''
  ) {
    if (rows[titlesFrom]) {
      const titles = [...Object.keys(rows[titlesFrom])]
      const rowsEnsure = [...rows] // 确保rows是数组，防止.map不可用
      return [
        titles, // 第一行先输出titles
        ...rowsEnsure.map((row, i) => {
          return titles.map((key) => parser(row[key], key, i)) // 每行按titles顺序输出数组
        })
      ]
    } else {
      return []
    }
  },
  /**
   * 强制关闭Excel应用
   */
  killExcel() {
    const childProcess = require('child_process')
    childProcess.exec('taskkill /f /im et.exe')
    childProcess.exec('taskkill /f /im excel.exe')
  },
  /**
   * 自动将数据写入excel
   *
   * @param {string} path xlsx文件的路径
   * @param {object[] | Array[String[]]} data *
   * @param {number}[titlesFrom=0] 从哪个index读取titles
   * @param {Function}[parser = (value, key, i) => value || ''] 传入(值 ,键, 行index)，返回该位置的值
   */
  writeABook(
    path,
    data,
    titlesFrom = 0,
    parser = (value, key, i) => value || ''
  ) {
    const { objArrToCsv } = exportObject
    const { Workbook } = require('xlsx-rw')
    const Fs = require('fs-extra')
    try {
      Fs.removeSync(path)
    } finally {
      //
    }
    Fs.ensureFileSync(path)
    const wb1 = new Workbook(path)
    if (
      data[0] &&
      /object\s+(object)/i.test(Object.prototype.toString.call(data[0]))
    ) {
      wb1.writeData(wb1.SheetNames[0], objArrToCsv(data, titlesFrom, parser))
    } else {
      wb1.writeData(wb1.SheetNames[0], data)
    }
    wb1.save(path)
  },
  readJSONSync(fullName, defaultTo = null) {
    const Fs = require('fs-extra')
    try {
      return JSON.parse(Fs.readFileSync(fullName, { encoding: 'utf-8' }))
    } catch (error) {
      return defaultTo
    }
  },
  getFileSize(path, digits = 1) {
    const Fs = require('fs-extra')
    let { size } = Fs.statSync(path)
    if (size > 1024 ** 3) {
      size = `${~~((size * 10 ** digits) / 1024 ** 3) / 10 ** digits}GB`
    } else if (size > 1024 ** 2) {
      size = `${~~((size * 10 ** digits) / 1024 ** 2) / 10 ** digits}MB`
    } else if (size > 1024) {
      size = `${~~((size * 10 ** digits) / 1024) / 10 ** digits}KB`
    } else {
      size = `${size}b`
    }
    return size
  }
}

module.exports = exportObject
